// dhcpprobe.cpp : Defines the entry point for the console application.
//

#include <acme/windows.h>
#include <Acme/scratch/ScratchConsole.h>
#include <Acme/scratch/ScratchSocket.h>

using namespace acme::windows;
using namespace acme::scratch;
#pragma comment(lib,"wsock32.lib")

// http://www.ietf.org/rfc/rfc2131.txt
// http://tools.ietf.org/html/rfc1497
// http://tools.ietf.org/html/rfc1533

class dhcp
{
public:
  enum{
    // op codes.
    BOOTREQUEST = 1,
    BOOTREPLY   = 2,
    // option flags for option 53 (DHCP message)
    DHCPDISCOVER = 1,
    DHCPOFFER    = 2,
    DHCPREQUEST  = 3,
    DHCPDECLINE  = 4,
    DHCPACK      = 5,
    DHCPNAK      = 6,
    DHCPRELEASE  = 7,
    // Option Commands
    optSubnetMask = 1,
    optRouter     = 3,
    optDomainNameServers = 6,
    optHostName   = 12,
    optDomainName = 15,
    optIPAddressLeaseTime = 51,
    optDHCPMessageType = 53,
    optServerID    = 54,
    optT1TimeValue = 58,
    optT2TimeValue = 59,
    optBroadcastAddress = 28,
    optEnd = 255,
    optPad = 0,
  };

  struct message
  {
    uint8_t op;
    uint8_t htype;
    uint8_t hlen;
    uint8_t hops;
    uint32_t xid;
    uint16_t secs;
    uint16_t flags;
    uint32_t ciaddr;
    uint32_t yiaddr;
    uint32_t siaddr;
    uint32_t giaddr;
    uint8_t chaddr[16];
    char sname[64];
    char file[128];
    // options (312)
    uint8_t magic[4];
    uint8_t opt[308];
  };
};


Socket::Initializer global;

#pragma comment(lib, "IPHLPAPI.lib")
#include <IPHlpApi.h>

#define MALLOC(x) HeapAlloc(GetProcessHeap(), 0, (x))
#define FREE(x) HeapFree(GetProcessHeap(), 0, (x))

/* Note: could also use malloc() and free() */

int GetMACAddress(int adapterIndex,void* buf,int len)
{
  IP_ADAPTER_INFO adapterInfo={0};
  ULONG outBufLen = sizeof (IP_ADAPTER_INFO);
  DWORD ret = GetAdaptersInfo(&adapterInfo, &outBufLen);
  if(!ret || ret == ERROR_BUFFER_OVERFLOW)
  {
    if(!buf)
      return adapterInfo.AddressLength;
    if(adapterInfo.AddressLength < (unsigned)len)
      len = adapterInfo.AddressLength;
    memcpy(buf,adapterInfo.Address,len);
    return len;
  }
  return 0;
}

int DumpAdapters(Console& con)
{

    /* Declare and initialize variables */

// It is possible for an adapter to have multiple
// IPv4 addresses, gateways, and secondary WINS servers
// assigned to the adapter. 
//
// Note that this sample code only prints out the 
// first entry for the IP address/mask, and gateway, and
// the primary and secondary WINS server for each adapter. 

    PIP_ADAPTER_INFO pAdapterInfo;
    PIP_ADAPTER_INFO pAdapter = NULL;
    DWORD dwRetVal = 0;
    UINT i;

/* variables used to print DHCP time info */
    struct tm newtime;
    char buffer[32];
    errno_t error;

    ULONG ulOutBufLen = sizeof (IP_ADAPTER_INFO);
    pAdapterInfo = (IP_ADAPTER_INFO *) MALLOC(sizeof (IP_ADAPTER_INFO));
    if (pAdapterInfo == NULL) {
        con.WriteFormat("Error allocating memory needed to call GetAdaptersinfo\n");
        return 1;
    }
// Make an initial call to GetAdaptersInfo to get
// the necessary size into the ulOutBufLen variable
    if (GetAdaptersInfo(pAdapterInfo, &ulOutBufLen) == ERROR_BUFFER_OVERFLOW) {
        FREE(pAdapterInfo);
        pAdapterInfo = (IP_ADAPTER_INFO *) MALLOC(ulOutBufLen);
        if (pAdapterInfo == NULL) {
            con.WriteFormat("Error allocating memory needed to call GetAdaptersinfo\n");
            return 1;
        }
    }

    if ((dwRetVal = GetAdaptersInfo(pAdapterInfo, &ulOutBufLen)) == NO_ERROR) {
        pAdapter = pAdapterInfo;
        while (pAdapter) {
            con.WriteFormat("\tComboIndex: \t%d\n", pAdapter->ComboIndex);
            con.WriteFormat("\tAdapter Name: \t%s\n", pAdapter->AdapterName);
            con.WriteFormat("\tAdapter Desc: \t%s\n", pAdapter->Description);
            con.WriteFormat("\tAdapter Addr: \t");
            for (i = 0; i < pAdapter->AddressLength; i++) {
                if (i == (pAdapter->AddressLength - 1))
                    con.WriteFormat("%.2X\n", (int) pAdapter->Address[i]);
                else
                    con.WriteFormat("%.2X-", (int) pAdapter->Address[i]);
            }
            con.WriteFormat("\tIndex: \t%d\n", pAdapter->Index);
            con.WriteFormat("\tType: \t");
            switch (pAdapter->Type) {
            case MIB_IF_TYPE_OTHER:
                con.WriteFormat("Other\n");
                break;
            case MIB_IF_TYPE_ETHERNET:
                con.WriteFormat("Ethernet\n");
                break;
            case MIB_IF_TYPE_TOKENRING:
                con.WriteFormat("Token Ring\n");
                break;
            case MIB_IF_TYPE_FDDI:
                con.WriteFormat("FDDI\n");
                break;
            case MIB_IF_TYPE_PPP:
                con.WriteFormat("PPP\n");
                break;
            case MIB_IF_TYPE_LOOPBACK:
                con.WriteFormat("Lookback\n");
                break;
            case MIB_IF_TYPE_SLIP:
                con.WriteFormat("Slip\n");
                break;
            default:
                con.WriteFormat("Unknown type %ld\n", pAdapter->Type);
                break;
            }

            con.WriteFormat("\tIP Address: \t%s\n",
                   pAdapter->IpAddressList.IpAddress.String);
            con.WriteFormat("\tIP Mask: \t%s\n", pAdapter->IpAddressList.IpMask.String);

            con.WriteFormat("\tGateway: \t%s\n", pAdapter->GatewayList.IpAddress.String);
            con.WriteFormat("\t***\n");

            if (pAdapter->DhcpEnabled) {
                con.WriteFormat("\tDHCP Enabled: Yes\n");
                con.WriteFormat("\t  DHCP Server: \t%s\n",
                       pAdapter->DhcpServer.IpAddress.String);

                con.WriteFormat("\t  Lease Obtained: ");
                /* Display local time */
                error = _localtime32_s(&newtime, (__time32_t*) &pAdapter->LeaseObtained);
                if (error)
                    con.WriteFormat("Invalid Argument to _localtime32_s\n");
                else {
                    // Convert to an ASCII representation 
                    error = asctime_s(buffer, 32, &newtime);
                    if (error)
                        con.WriteFormat("Invalid Argument to asctime_s\n");
                    else
                        /* asctime_s returns the string terminated by \n\0 */
                        con.WriteFormat("%s", buffer);
                }

                con.WriteFormat("\t  Lease Expires:  ");
                error = _localtime32_s(&newtime, (__time32_t*) &pAdapter->LeaseExpires);
                if (error)
                    con.WriteFormat("Invalid Argument to _localtime32_s\n");
                else {
                    // Convert to an ASCII representation 
                    error = asctime_s(buffer, 32, &newtime);
                    if (error)
                        con.WriteFormat("Invalid Argument to asctime_s\n");
                    else
                        /* asctime_s returns the string terminated by \n\0 */
                        con.WriteFormat("%s", buffer);
                }
            } else
                con.WriteFormat("\tDHCP Enabled: No\n");

            if (pAdapter->HaveWins) {
                con.WriteFormat("\tHave Wins: Yes\n");
                con.WriteFormat("\t  Primary Wins Server:    %s\n",
                       pAdapter->PrimaryWinsServer.IpAddress.String);
                con.WriteFormat("\t  Secondary Wins Server:  %s\n",
                       pAdapter->SecondaryWinsServer.IpAddress.String);
            } else
                con.WriteFormat("\tHave Wins: No\n");
            pAdapter = pAdapter->Next;
            con.WriteFormat("\n");
        }
    } else {
        con.WriteFormat("GetAdaptersInfo failed with error: %d\n", dwRetVal);

    }
    if (pAdapterInfo)
        FREE(pAdapterInfo);

    return 0;
}




void DumpTo(Console& con, dhcp::message& dhcpmsg, int recvbytes)
{
  con.WriteFormat(TEXT("Dumping DHCP Packet (%d bytes)\r\n"),recvbytes);
  TCHAR buf[1025];
  LPCTSTR value;
  switch(dhcpmsg.op){
  case 1: value = TEXT("BOOTREQEST"); break;
  case 2: value = TEXT("BOOTREPLY"); break;
  default:
    value=buf;
    wsprintf(buf,TEXT("(0x%02x)"),dhcpmsg.op);
  }
  con.WriteFormat(TEXT("op:%s\r\n"),value);

  switch(dhcpmsg.htype){
  case 1: value = TEXT("10mb ethernet"); break;
  default:
    value=buf;
    wsprintf(buf,TEXT("(0x%02x)"),dhcpmsg.htype);
  }
  con.WriteFormat(TEXT("hwtype:'%s', hlen:%d\r\n"),value,dhcpmsg.hlen);
  con.WriteFormat(TEXT("hops:%d\r\n"),dhcpmsg.hops);
  con.WriteFormat(TEXT("xid:%x\r\n"),ntohl(dhcpmsg.xid));
  con.WriteFormat(TEXT("secs:%d\r\n"),ntohs(dhcpmsg.secs));
  con.WriteFormat(TEXT("flags:0x%08x\r\n"),ntohl(dhcpmsg.flags));
  in_addr ia; ia.S_un.S_addr = dhcpmsg.ciaddr;
  con.WriteFormat(TEXT("ciaddr:%S\r\n"),inet_ntoa(ia));
  ia.S_un.S_addr = dhcpmsg.yiaddr;
  con.WriteFormat(TEXT("yiaddr:%S\r\n"),inet_ntoa(ia));
  ia.S_un.S_addr = dhcpmsg.siaddr;
  con.WriteFormat(TEXT("siaddr:%S\r\n"),inet_ntoa(ia));
  ia.S_un.S_addr = dhcpmsg.giaddr;
  con.WriteFormat(TEXT("giaddr:%S\r\n"),inet_ntoa(ia));
  con.WriteFormat(TEXT("chaddr:%02x-%02x-%02x-%02x-%02x-%02x\r\n"),dhcpmsg.chaddr[0],dhcpmsg.chaddr[2],dhcpmsg.chaddr[3],dhcpmsg.chaddr[4],dhcpmsg.chaddr[5],dhcpmsg.chaddr[6]);
  con.WriteFormat(TEXT("sname:%S\r\n"),dhcpmsg.sname);
  con.WriteFormat(TEXT("file:%S\r\n"),dhcpmsg.file);
  int len = recvbytes - ((char*)&dhcpmsg.opt-(char*)&dhcpmsg);
  con.WriteFormat(TEXT("Options (%d bytes):\r\n-magic:%02x.%02x.%02x.%02x\r\n- "),len+4,dhcpmsg.magic[0],dhcpmsg.magic[1],dhcpmsg.magic[2],dhcpmsg.magic[3]);

  for(int i=0; i<len; )
  {
    switch(dhcpmsg.opt[i])
    {
      const TCHAR* name;
    case 1:
    case 28:
      if(dhcpmsg.opt[i]==1) value=TEXT("Subnet Mask");
      else if(dhcpmsg.opt[i]==28) value=TEXT("Broadcast");
      ia.S_un.S_addr = *(u_long*)(&dhcpmsg.opt[i+2]);
      con.WriteFormat(TEXT("%s: %S\r\n- "),value,  inet_ntoa(ia));
      i+=6;
      break;
    case 12:
      name=TEXT("Hostname");
      if(false){
    case 15:
        name=TEXT("Domain");
      }
      value=buf; wsprintf(buf,TEXT("%s: %%.%dS\r\n- "),name,dhcpmsg.opt[i+1]);
      con.WriteFormat(value,&dhcpmsg.opt[i+2]);
      i+=2+dhcpmsg.opt[i+1];
      break;

    case 3:
    case 6:
      if(dhcpmsg.opt[i]==3)
        con.WriteFormat(TEXT("Gateway: "));
      else //if(dhcpmsg.opt[i]==6)
        con.WriteFormat(TEXT("DNS Servers: "));
      int x;
      x=dhcpmsg.opt[i+1]/4;
      i+=2;
      for(;x;x--){
        ia.S_un.S_addr = *(u_long*)(&dhcpmsg.opt[i]);
        con.WriteFormat(TEXT("%S "), inet_ntoa(ia));
        i+=4;
      }
      con.WriteFormat(TEXT("\r\n- "));
      break;
    case 51:
    case 58:
    case 59:
      if(dhcpmsg.opt[i]==51) value=TEXT("IP Address Lease Time");
      else if(dhcpmsg.opt[i]==58) value=TEXT("Renewal Time");
      else if(dhcpmsg.opt[i]==59) value=TEXT("Rebind Time");
      con.WriteFormat(TEXT("%s: %ds\r\n- "),value, *(u_long*)(&dhcpmsg.opt[i]));
      i+=6;
      break;
    case 53:
      switch(dhcpmsg.opt[i+2]){
      case 1: value=TEXT("DHCPDISCOVER"); break;
      case 2: value=TEXT("DHCPOFFER"); break;
      case 3: value=TEXT("DHCPREQUEST"); break;
      case 4: value=TEXT("DHCPDECLINE"); break;
      case 5: value=TEXT("DHCPACK"); break;
      case 6: value=TEXT("DHCPNAK"); break;
      case 7: value=TEXT("DHCPRELEASE"); break;
      }
      con.WriteFormat(TEXT("DHCP Message: %s\r\n- "), value);
      i+=3;
      break;
    case 54:
      ia.S_un.S_addr = *(u_long*)(&dhcpmsg.opt[i+2]);
      con.WriteFormat(TEXT("Server ID: %S\r\n- "), inet_ntoa(ia));
      i+=6;
      break;
    case 255:
      i++;
      break;
    default:
      con.WriteFormat(TEXT("%02x "),dhcpmsg.opt[i++]);
    }
  
  }

  con.WriteFormat(TEXT("\r\n"));


}


int main()
{
  Socket* dhcpSocket = new Socket(AF_INET,SOCK_DGRAM,IPPROTO_UDP);
  dhcpSocket->SetSockOpt(SOL_SOCKET,SO_REUSEADDR,1);
  dhcpSocket->SetSockOpt(SOL_SOCKET,SO_BROADCAST,1);
  dhcpSocket->Bind(68);
  Socket::Address serverAddr(INADDR_BROADCAST,67);
  struct dhcp::message dhcpmsg = {0};
  dhcpmsg.op = 1;
  dhcpmsg.htype = 1;
  dhcpmsg.hlen = 6;
  dhcpmsg.hops = 0;
  dhcpmsg.xid = htonl(1000);
  dhcpmsg.secs = htons(0);
  dhcpmsg.flags = htons(0x8000);
  GetMACAddress(0,dhcpmsg.chaddr,sizeof dhcpmsg.chaddr);
  //dhcpmsg.chaddr[0] = 0x00;
  //dhcpmsg.chaddr[1] = 0x1A;
  //dhcpmsg.chaddr[2] = 0x80;
  //dhcpmsg.chaddr[3] = 0x80;
  //dhcpmsg.chaddr[4] = 0x2C;
  //dhcpmsg.chaddr[5] = 0xC3;
  dhcpmsg.magic[0]=99;
  dhcpmsg.magic[1]=130;
  dhcpmsg.magic[2]=83;
  dhcpmsg.magic[3]=99;
  dhcpmsg.opt[0]=dhcp::optDHCPMessageType;
  dhcpmsg.opt[1]=1;
  dhcpmsg.opt[2]=dhcp::DHCPDISCOVER;
  dhcpmsg.opt[3]=dhcp::optHostName;
  //dhcpmsg.opt[4]=1;
  gethostname((char*)&dhcpmsg.opt[5],64);
  int namelen = strlen((char*)&dhcpmsg.opt[5]);
  dhcpmsg.opt[4] = namelen;
  int len = (char*)&dhcpmsg.opt[5+namelen]-(char*)&dhcpmsg;

  Console con;
  if(false)
    DumpAdapters(con);
  con.WriteFormat(TEXT("DHCP Probe\r\nSending Probe Request:\r\n"));
  DumpTo(con,dhcpmsg,len);

  dhcpSocket->SendTo(&dhcpmsg,len,&serverAddr);

  int packets =0;
  int max_tries=5;
  int timeout=5000;

  for(int packets=0; packets<max_tries; packets++)
  {
    fd_set readfs;
    FD_ZERO(&readfs);
    FD_SET(dhcpSocket->fd,&readfs);

    timeval timeout={0};
    timeout.tv_sec=1;
    if(select(1,&readfs,0,0,&timeout)>=1)
    {
      con.WriteFormat(TEXT("Got a Response:\r\n"));

      struct dhcp::message recvdhcpmsg={0};
      Socket::Address fromAddr;
      int recvbytes = dhcpSocket->RecvFrom(&recvdhcpmsg,sizeof recvdhcpmsg,&fromAddr);
      DumpTo(con,recvdhcpmsg,recvbytes);
    }
    else
    {
      con.WriteFormat(TEXT("."));
    }
  }
  con.WriteFormat(TEXT("\r\n"));

  delete dhcpSocket;

	return 0;
}

